package EA;
import EA.Framework;
import EA.UsefulRoutines;
import java.lang.Math;
import RKUjava.math.RKURandom;
import java.util.BitSet;
import RKUjava.lang.*;
import RKUjava.math.*;

/** Abstract class for numerical optimization EA's */
public class BitNumericalGenome extends NumericalGenome
{
  public BitSet[] chromosomes;
  public BitNumericalFramework bnf;
  /** Create a new bitnumerical genome with a random initial value. 
      @param frame The framework for the EA this genome is a part of. 
      This must be a subclass of BitNumericalFramework. frame must also be
      properly initialized with numeber of dimensions etc.
      @param initialize True if you want random initialization.
  */
  public BitNumericalGenome(Framework frame, boolean initialize)
    {
      super(frame);
      bnf = (BitNumericalFramework)frame;
      { 
	int dim = bnf.dimensions;
	int i,j;

	chromosomes = new BitSet[dim];
	for (i=0;i<dim;i++) {
	  chromosomes[i] = new BitSet(bnf.bits_per_variable[i]);

	  if (initialize) {
	    for (j=0; j<bnf.bits_per_variable[i];j++) {
	      if (UsefulRoutines.randomBoolean())
		(chromosomes[i]).clear(j);
	      else
		(chromosomes[i]).set(j);
	    };
	  };

	};
      };
      //      System.out.println(this.toString2());
    };

  /** Don't override this unless you know what you are doing. 
      The class implements this method which updates its bitvectors according
      to the values in realpos.
   */
  public void NumericalGenome_setPos_inner()
    {
      int i,j;
      long temp;

      for (i=0;i<bnf.dimensions;i++) {
	temp = (long) (((realpos[i]-bnf.intervals[i].min)/bnf.intervals[i].getLength())*bnf.maxvalue_per_variable[i]);
	for(j=bnf.bits_per_variable[i]-1;j>=0;j--) {
	  if ((temp&1)==1)
	    chromosomes[i].set(j);
	  else
	    chromosomes[i].clear(j);
	  temp = temp>>1;
	}
      }
    };

  /** Don't override this unless you know what you are doing. 
      The class implements this method which converts the bitvectors to 
      doubles and updates the realpos array.
   */
  public void NumericalGenome_updatePos_inner()
    {
      int i,j;
      long temp;

      for (i=0;i<bnf.dimensions;i++) {
	temp = 0;
	for(j=0;j<bnf.bits_per_variable[i];j++) {
	  temp = temp<<1;
	  if (chromosomes[i].get(j))
	    temp++;
	}
	realpos[i] = (bnf.intervals[i].min*bnf.maxvalue_per_variable[i]+
		      (bnf.intervals[i].getLength()*temp))/bnf.maxvalue_per_variable[i];
      };
    };

  /** Don't override this unless you know what you are doing. <br> 
      The class implements single point crossover on the genome.
   */
  public static Genome crossover(Genome genome1, Genome genome2)
    {
      int[] arr = {RKURandom.randomInt(((BitNumericalGenome)genome1).bnf.totalbits)};

      return crossover(genome1, genome2, arr);
    }

  /** Performs n-point crossover between genome1 and genome2. 
      The method takes it first part from a randomly chosen (between the 
      two) genome and then alternates between the two genomes.
      @param numcpoints 

  */
  public static synchronized Genome crossover(Genome genome1, Genome genome2, int numcpoints)
    {
      int[] arr = new int[numcpoints];
      int i, idx;
      int totalbits = ((BitNumericalGenome)genome1).bnf.totalbits;
      BitNumericalFramework bnf = ((BitNumericalGenome)genome1).bnf;

      // Generate random numbers
      i=0;
      while (i<numcpoints) {
	idx = RKURandom.randomInt(totalbits);
	
	if (!bnf.crossoverpoints[idx]) {
	  bnf.crossoverpoints[idx] = true;
	  arr[i] = idx;
	  i++;
	};
      };

      UsefulRoutines.QSort(arr);

      // Reset the crossoverpoints bitvector
      for (i=0;i<numcpoints;i++) {
	bnf.crossoverpoints[arr[i]] = false;
      }
      return crossover(genome1, genome2, arr);
    }


  /** Performs n-point crossover between genome1 and genome2. 
      The method takes it first part from a randomly chosen (between the 
      two) genome and then alternates between the two genomes.
      @param cpoints array of crossoverpoints. All entries must lie in the
      range [0,totalbits]. The bits are taken up to but not including the
      splitpoints, i.e., a vector of [0] will take all bits from one of the
      parents. (The one <b>not</b> chosen to donate first part of the bits.)
  */
  public static BitNumericalGenome crossover(Genome genome1, Genome genome2, int[] cpoints)
    {
      int i, curvar;
      BitNumericalGenome offspring, parent1, parent2, current;
      int bitssofar, nextsplit;
      BitNumericalFramework bnf = ((BitNumericalGenome)genome1).bnf;

      offspring = new BitNumericalGenome(genome1.framework, false);
      if (UsefulRoutines.randomBoolean()) {
	parent1 = (BitNumericalGenome)genome1;
	parent2 = (BitNumericalGenome)genome2;
      }
      else {
	parent1 = (BitNumericalGenome)genome2;
	parent2 = (BitNumericalGenome)genome1;
      }

      current = parent1;

      curvar = 0;
      bitssofar = 0;
      nextsplit = 0;
      for (i=0;i<bnf.totalbits;i++) {
	if(i>=bitssofar+bnf.bits_per_variable[curvar]) {
	  bitssofar = bitssofar+bnf.bits_per_variable[curvar];
	  curvar++;
	}

	/* Swap parents if splitpoint reached */
	if (nextsplit<cpoints.length) {
	  if (i==cpoints[nextsplit]) {
	    if (current == parent1)
	      current = parent2;
	    else
	      current = parent1;	      
	    nextsplit++;
	  }
	}

	if (current.chromosomes[curvar].get(i-bitssofar)) {
	  offspring.chromosomes[curvar].set(i-bitssofar);
	}
	else {
	  offspring.chromosomes[curvar].clear(i-bitssofar);
	};
      }

      return offspring;
    }

  /** Perform mutation bit by bit. Use mutate(p_m) from class Genome to invoke
      the mutation. The method runs through all bits and 
      flip them with probability p_m.
      @param p_m Probability to flip a bit.
  */
  public void Genome_mutate_inner(double p_m)
    {
      int i,j;

      realposcalculated = false;
      for (i=0;i<bnf.dimensions;i++) {
	for(j=0;j<bnf.bits_per_variable[i];j++) {
	  if(UsefulRoutines.randomBiasedBoolean(p_m)) {
	    if(chromosomes[i].get(j))
	      chromosomes[i].clear(j);
	    else
	      chromosomes[i].set(j);
	  }
	}
      }
    }

  public String toString2()
    {
      String res, r;
      updatePos();

      res = "(";
      for (int i=0;i<bnf.dimensions;i++) {
	r = "";
	for (int j=0;j<bnf.bits_per_variable[i];j++) {
	  if (chromosomes[i].get(j))
	    r = r+"1";
	  else
	    r = r+"0";
	}
	res = res+r;

	if (i+1<bnf.dimensions) {
	  res = res + ", ";
	}
      }

      return res+")";
      
    }

  public Object clone()
    {
      BitNumericalGenome res;
      int i;

      res = new BitNumericalGenome(framework, false);

      for (i=0;i<chromosomes.length;i++) {
	res.chromosomes[i] = (BitSet)this.chromosomes[i].clone();
	res.realpos[i] = this.realpos[i];
      }

      res.fitness = this.fitness;
      res.fitnesscalculated = this.fitnesscalculated;
      res.realposcalculated = this.realposcalculated;
      return res;
    }

}



